﻿//---------------------------------------------------------------------------
//
// Copyright (C) Microsoft Corporation.  All rights reserved.
//
// File: RibbonTab.cs
//
//---------------------------------------------------------------------------

#region Using declarations

using System;
using System.Collections;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Markup;

#endregion

namespace Microsoft.Windows.Controls.Ribbon
{
    [ContentProperty("Groups")]
    public class RibbonTab : Control
    {
        #region Constructor
        static RibbonTab()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(RibbonTab), new FrameworkPropertyMetadata(typeof(RibbonTab)));
        }
        
        public RibbonTab()
        {
        }

        #endregion

        #region Properties

        #region DefaultPadding property
        internal Thickness DefaultPadding
        {
            get
            {
                if (double.IsNaN(_initialPadding.Left))
                {
                    _initialPadding = Padding;
                }

                return _initialPadding;
            }
        }
        #endregion

        #region ShowLabelToolTip
        internal bool ShowLabelToolTip
        {
            get
            {
                return Label != null && Label.Equals(ToolTip);
            }

            set
            {
                if (value)
                {
                    if (ToolTip == null)
                    {
                        ToolTip = Label;
                    }
                }
                else
                {
                    if (ShowLabelToolTip)
                    {
                        ToolTip = null;
                    }
                }
            }
        }
        #endregion

        #region GroupSizeReductionOrder property
        [TypeConverter(typeof(StringCollectionConverter))]
        public StringCollection GroupSizeReductionOrder
        {
            get { return (StringCollection)GetValue(GroupSizeReductionOrderProperty); }
            set { SetValue(GroupSizeReductionOrderProperty, value); }
        }

        public static readonly DependencyProperty GroupSizeReductionOrderProperty =
                DependencyProperty.Register(
                            "GroupSizeReductionOrder",
                            typeof(StringCollection),
                            typeof(RibbonTab),
                            new FrameworkPropertyMetadata(null));
        #endregion

        #region Label property
        public string Label
        {
            get { return (string)GetValue(LabelProperty); }
            set { SetValue(LabelProperty, value); }
        }

        public static readonly DependencyProperty LabelProperty =
                DependencyProperty.Register(
                            "Label",
                            typeof(string),
                            typeof(RibbonTab),
                            new FrameworkPropertyMetadata(String.Empty));
        #endregion

        #region IsSelected property
        public bool IsSelected
        {
            get { return (bool)GetValue(IsSelectedProperty); }
            set { SetValue(IsSelectedProperty, value); }
        }

        public static readonly DependencyProperty IsSelectedProperty =
                DependencyProperty.Register(
                            "IsSelected",
                            typeof(bool),
                            typeof(RibbonTab),
                            new FrameworkPropertyMetadata(false, new PropertyChangedCallback(OnIsSelectedChanged)));

        private static void OnIsSelectedChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            RibbonTab ribbonTab = (RibbonTab)sender;
            bool isSelected = ribbonTab.IsSelected;

            // Update Ribbon.SelectedTab
            if (isSelected)
            {
                Ribbon ribbon = ribbonTab.Ribbon;
                if (ribbon != null)
                {
                    ribbon.SelectedTab = ribbonTab;
                }
            }

            // Raise Selected/Unselected event
            ribbonTab.RaiseEvent(new RoutedEventArgs(isSelected ? SelectedEvent : UnselectedEvent));
        }
        #endregion

        #region IsOpen property

        // Read-only property returns Ribbon.IsOpen - used in template triggers
        private static readonly DependencyPropertyKey IsOpenPropertyKey =
                DependencyProperty.RegisterReadOnly(
                            "IsOpen",
                            typeof(bool),
                            typeof(RibbonTab),
                            new FrameworkPropertyMetadata(true, null, new CoerceValueCallback(OnCoerceIsOpen)));

        public static readonly DependencyProperty IsOpenProperty =
                IsOpenPropertyKey.DependencyProperty;

        private static object OnCoerceIsOpen(DependencyObject sender, object value)
        {
            RibbonTab tab = (RibbonTab)sender;
            Ribbon ribbon = tab.Ribbon;

            if (ribbon != null)
            {
                return ribbon.IsOpen;
            }
            
            return value;
        }

        public bool IsOpen
        {
            get { return (bool)GetValue(IsOpenProperty); }
        }
        #endregion

        #region Groups property
        public Collection<RibbonGroup> Groups
        {
            get
            {
                if (_groups == null)
                {
                    _groups = new ObservableCollection<RibbonGroup>();
                    _groups.CollectionChanged += new NotifyCollectionChangedEventHandler(OnGroupCollectionChanged);
                }

                return _groups;
            }
        }

        private void OnGroupCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            RibbonGroupsContainer ribbonGroupsContainer = RibbonGroupsContainer;
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Reset:
                    ribbonGroupsContainer.Children.Clear();
                    foreach (RibbonGroup group in Groups)
                    {
                        ribbonGroupsContainer.Children.Add(group);
                    }

                    break;
                case NotifyCollectionChangedAction.Add:
                    foreach (RibbonGroup group in e.NewItems)
                    {
                        ribbonGroupsContainer.Children.Add(group);
                    }

                    break;
                case NotifyCollectionChangedAction.Remove:
                    foreach (RibbonGroup group in e.OldItems)
                    {
                        ribbonGroupsContainer.Children.Remove(group);
                    }

                    break;
                case NotifyCollectionChangedAction.Replace:
                    if (e.NewItems.Count == 1 && e.OldItems.Count == 1)
                    {
                        int index = ribbonGroupsContainer.Children.IndexOf(e.OldItems[0] as RibbonGroup);
                        if (index >= 0)
                        {
                            RibbonGroup group = e.NewItems[0] as RibbonGroup;
                            ribbonGroupsContainer.Children[index] = group;
                        }
                    }

                    break;
            }
        }
        #endregion

        #endregion

        #region Public Events

        #region Selected Event
        public static readonly RoutedEvent SelectedEvent = EventManager.RegisterRoutedEvent("Selected", RoutingStrategy.Direct, typeof(RoutedEventHandler), typeof(RibbonTab));

        public event RoutedEventHandler Selected
        {
            add
            {
                AddHandler(SelectedEvent, value, false);
            }

            remove
            {
                RemoveHandler(SelectedEvent, value);
            }
        }
        #endregion

        #region Unselected Event
        public static readonly RoutedEvent UnselectedEvent = EventManager.RegisterRoutedEvent("Unselected", RoutingStrategy.Direct, typeof(RoutedEventHandler), typeof(RibbonTab));

        public event RoutedEventHandler Unselected
        {
            add
            {
                AddHandler(UnselectedEvent, value, false);
            }

            remove
            {
                RemoveHandler(UnselectedEvent, value);
            }
        }
        #endregion

        #endregion

        #region Private Helpers
        // RibbonGroupsContainer is created on demand - it is not visually connected to RibbonTab.
        // It is hosted under Ribbon when RibbonTab gets selected
        internal RibbonGroupsContainer RibbonGroupsContainer
        {
            get
            {
                if (_ribbonGroupsContainer == null)
                {
                    _ribbonGroupsContainer = new RibbonGroupsContainer(this);
                }

                return _ribbonGroupsContainer;
            }
        }

        private RibbonGroupsContainer _ribbonGroupsContainer;

        private Ribbon Ribbon
        {
            get
            {
                Ribbon ribbon = Parent as Ribbon;
                if (ribbon != null)
                {
                    return ribbon;
                }

                RibbonContextualTabGroup ribbonContextualTabGroup = Parent as RibbonContextualTabGroup;
                if (ribbonContextualTabGroup != null)
                {
                    return ribbonContextualTabGroup.Parent as Ribbon;
                }

                return null;
            }
        }
        #endregion

        #region Input
        protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
        {
            if (Ribbon != null)
            {
                Ribbon.NotifyMouseClickedOnTab(this, e);
                e.Handled = true;
            }

            base.OnMouseLeftButtonDown(e);
        }
        #endregion

        #region Hookup Logical Tree
        protected override IEnumerator LogicalChildren
        {
            get
            {
                return Groups.GetEnumerator();
            }
        }
        #endregion

        #region Resizing Logic
        /// <summary>
        ///     If the application developer has specified a GroupSizeReductionOrder, this
        ///     takes the next group in that order and tells it to increase to its next size.
        ///     If no GroupSizeReductionOrder was specified, or if we collapsed RibbonGroups
        ///     beyond what was specified by the developer, we expand groups in reverse order
        ///     of their reduction.
        /// </summary>
        /// <returns>True if a group was able to be expanded in size, false otherwise.</returns>
        internal bool IncreaseNextGroupSize()
        {
            bool resizeSuccessful = false;

            int automaticResizeOrderCount = _automaticResizeOrder.Count;
            if ((GroupSizeReductionOrder != null) && (_groupReduceOrderLocation >= 0) && (automaticResizeOrderCount == 0))
            {
                // Find the RibbonGroup whose name is specified next in the GroupSizeReductionOrder.
                string nextGroup = GroupSizeReductionOrder[_groupReduceOrderLocation];
                RibbonGroup targetGroup = null;
                foreach (RibbonGroup group in Groups)
                {
                    if (group.Name == nextGroup)
                    {
                        targetGroup = group;
                        break;
                    }
                }

                if (targetGroup == null)
                {
                    // TODO - Appropriate exception?
                    throw new InvalidOperationException("Invalid group name: " + nextGroup);
                }

                // A group was found, tell it to increase its size.
                resizeSuccessful = targetGroup.IncreaseGroupSize();
                _groupReduceOrderLocation--;
            }
            else
            {
                // Either no group size reduction order is specified, no groups have been decreased in size
                // yet, or we have groups that have been automatically reduced in size which must be expanded
                // before returning to the user-defined orderings.
                if (automaticResizeOrderCount > 0)
                {
                    int nextGroupIndex = _automaticResizeOrder[automaticResizeOrderCount - 1];
                    _automaticResizeOrder.RemoveAt(automaticResizeOrderCount - 1);
                    resizeSuccessful = Groups[nextGroupIndex].IncreaseGroupSize();
                    Debug.Assert(resizeSuccessful);
                    _groupAutoResizeIndex = nextGroupIndex;
                }
            }

            return resizeSuccessful;
        }

        /// <summary>
        ///     If the application developer has specified a GroupSizeReductionOrder, this
        ///     takes the next group in that order and tells it to reduce to its next size.
        ///     If no GroupSizeReductionOrder was specified, or if we need to collapse RibbonGroups
        ///     beyond what was specified by the developer, we reduce groups from right-to-left,
        ///     step by step in cyclical order.
        /// </summary>
        /// <returns>
        ///     Returns true if a group was located and resized successfully, false otherwise.
        /// </returns>
        internal bool DecreaseNextGroupSize()
        {
            bool resizeSuccessful = false;

            if (GroupSizeReductionOrder != null && (_groupReduceOrderLocation < GroupSizeReductionOrder.Count - 1))
            {
                _groupReduceOrderLocation++;

                // Find the group who's next to be reduced.
                string nextGroup = GroupSizeReductionOrder[_groupReduceOrderLocation];
                RibbonGroup targetGroup = null;
                foreach (RibbonGroup group in Groups)
                {
                    if (group.Name == nextGroup)
                    {
                        targetGroup = group;
                        break;
                    }
                }

                if (targetGroup == null)
                {
                    // TODO - Appropriate exception?
                    throw new InvalidOperationException("Invalid group name: " + nextGroup);
                }

                resizeSuccessful = targetGroup.DecreaseGroupSize();
            }
            else
            {
                // Either no GroupSizeReductionOrder was specified, or we've run out of predefined orderings.
                // In this case we should begin reducing groups in size right-to-left, step by step, in cyclical
                // order.
                resizeSuccessful = DefaultCyclicalReduceGroup();
            }

            return resizeSuccessful;
        }

        /// <summary>
        ///     From right-to-left, finds the next group who can be reduced in size.  If the leftmost
        ///     group is encountered reduction will continue in a cyclical fashion back at the rightmost
        ///     RibbonGroup.
        /// </summary>
        /// <returns>True if a group was successfully located and reduced in size, false otherwise.</returns>
        private bool DefaultCyclicalReduceGroup()
        {
            bool resizeSuccessful = false;

            if (_groupAutoResizeIndex == null)
            {
                _groupAutoResizeIndex = Groups.Count - 1;
            }

            bool resizesRemain = true;

            while (resizesRemain && !resizeSuccessful)
            {
                int numAttempts = 0;
                do
                {
                    numAttempts++;
                    resizeSuccessful = Groups[(_groupAutoResizeIndex--).Value].DecreaseGroupSize();

                    if (resizeSuccessful == true)
                    {
                        _automaticResizeOrder.Add(_groupAutoResizeIndex.Value + 1);
                    }

                    // If we have underflowed our Groups collection, start again at the end.  This
                    // is what makes our group reduction cyclical.
                    if (_groupAutoResizeIndex.Value < 0)
                    {
                        _groupAutoResizeIndex = Groups.Count - 1;
                        break;
                    }
                } while (resizeSuccessful == false);

                // If we failed to resize during this pass, and we attempted to resize for every
                // group, then there are no reamining groups to resize.
                if (numAttempts == Groups.Count)
                {
                    resizesRemain = false;
                }
            }

            return resizeSuccessful;
        }
        #endregion

        #region Private Data
        private ObservableCollection<RibbonGroup> _groups;
        private Thickness _initialPadding = new Thickness(double.NaN);
        private int _groupReduceOrderLocation = -1;

        private int? _groupAutoResizeIndex;
        private Collection<int> _automaticResizeOrder = new Collection<int>();
        #endregion
    }
}